//===--- GenRecord.h - IR generation for record types -----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file provides some common code for emitting record types.
//  A record type is something like a tuple or a struct.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_IRGEN_INTERNAL_GENRECORD_H
#define POLARPHP_IRGEN_INTERNAL_GENRECORD_H

#include "polarphp/irgen/internal/BitPatternBuilder.h"
#include "polarphp/irgen/internal/IRGenFunction.h"
#include "polarphp/irgen/internal/IRGenModule.h"
#include "polarphp/irgen/internal/Explosion.h"
#include "polarphp/irgen/internal/GenEnum.h"
#include "polarphp/irgen/internal/GenOpaque.h"
#include "polarphp/irgen/internal/LoadableTypeInfo.h"
#include "polarphp/irgen/internal/Outlining.h"
#include "polarphp/irgen/internal/TypeInfo.h"
#include "polarphp/irgen/internal/StructLayout.h"
#include "llvm/Support/TrailingObjects.h"

namespace polar::irgen {

template <class, class, class> class RecordTypeBuilder;

/// A field of a record type.
template <class FieldImpl> class RecordField {
   ElementLayout Layout;

   template <class, class, class> friend class RecordTypeBuilder;

   /// Begin/End - the range of explosion indexes for this element
   unsigned Begin : 16;
   unsigned End : 16;

protected:
   explicit RecordField(const TypeInfo &elementTI)
      : Layout(ElementLayout::getIncomplete(elementTI)) {}

   explicit RecordField(const ElementLayout &layout,
                        unsigned begin, unsigned end)
      : Layout(layout), Begin(begin), End(end) {}

   const FieldImpl *asImpl() const {
      return static_cast<const FieldImpl*>(this);
   }
public:
   const TypeInfo &getTypeInfo() const { return Layout.getType(); }

   void completeFrom(const ElementLayout &layout) {
      Layout.completeFrom(layout);
   }

   bool isEmpty() const {
      return Layout.isEmpty();
   }

   IsPOD_t isPOD() const {
      return Layout.isPOD();
   }

   IsABIAccessible_t isABIAccessible() const {
      return Layout.getType().isABIAccessible();
   }

   Address projectAddress(IRGenFunction &IGF, Address seq,
                          NonFixedOffsets offsets) const {
      return Layout.project(IGF, seq, offsets, "." + asImpl()->getFieldName());
   }

   ElementLayout::Kind getKind() const {
      return Layout.getKind();
   }

   bool hasFixedByteOffset() const {
      return Layout.hasByteOffset();
   }

   Size getFixedByteOffset() const {
      return Layout.getByteOffset();
   }

   unsigned getStructIndex() const { return Layout.getStructIndex(); }

   unsigned getNonFixedElementIndex() const {
      return Layout.getNonFixedElementIndex();
   }

   std::pair<unsigned, unsigned> getProjectionRange() const {
      return {Begin, End};
   }
};

enum FieldsAreABIAccessible_t : bool {
   FieldsAreNotABIAccessible = false,
   FieldsAreABIAccessible = true,
};

/// A metaprogrammed TypeInfo implementation for record types.
template <class Impl, class Base, class FieldImpl_,
   bool IsLoadable = std::is_base_of<LoadableTypeInfo, Base>::value>
class RecordTypeInfoImpl : public Base,
                           private llvm::TrailingObjects<Impl, FieldImpl_> {
   friend class llvm::TrailingObjects<Impl, FieldImpl_>;

public:
   using FieldImpl = FieldImpl_;

private:
   const unsigned NumFields;
   const unsigned AreFieldsABIAccessible : 1;

   mutable Optional<const FieldImpl *> ExtraInhabitantProvidingField;
   mutable Optional<bool> MayHaveExtraInhabitants;

protected:
   const Impl &asImpl() const { return *static_cast<const Impl*>(this); }

   template <class... As>
   RecordTypeInfoImpl(ArrayRef<FieldImpl> fields,
                      FieldsAreABIAccessible_t fieldsABIAccessible,
                      As&&...args)
      : Base(std::forward<As>(args)...),
        NumFields(fields.size()),
        AreFieldsABIAccessible(fieldsABIAccessible) {
      std::uninitialized_copy(fields.begin(), fields.end(),
                              this->template getTrailingObjects<FieldImpl>());
   }

public:
   /// Allocate and initialize a type info of this type.
   template <class... As>
   static Impl *create(ArrayRef<FieldImpl> fields, As &&...args) {
      size_t size = Impl::template totalSizeToAlloc<FieldImpl>(fields.size());
      void *buffer = ::operator new(size);
      return new(buffer) Impl(fields, std::forward<As>(args)...);
   }

   ArrayRef<FieldImpl> getFields() const {
      return {this->template getTrailingObjects<FieldImpl>(), NumFields};
   }

   /// The standard schema is just all the fields jumbled together.
   void getSchema(ExplosionSchema &schema) const override {
      for (auto &field : getFields()) {
         field.getTypeInfo().getSchema(schema);
      }
   }

   void assignWithCopy(IRGenFunction &IGF, Address dest, Address src, PILType T,
                       bool isOutlined) const override {
      // If the fields are not ABI-accessible, use the value witness table.
      if (!AreFieldsABIAccessible) {
         return emitAssignWithCopyCall(IGF, T, dest, src);
      }

      if (isOutlined || T.hasOpenedExistential()) {
         auto offsets = asImpl().getNonFixedOffsets(IGF, T);
         for (auto &field : getFields()) {
            if (field.isEmpty())
               continue;

            Address destField = field.projectAddress(IGF, dest, offsets);
            Address srcField = field.projectAddress(IGF, src, offsets);
            field.getTypeInfo().assignWithCopy(
               IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined);
         }
      } else {
         this->callOutlinedCopy(IGF, dest, src, T, IsNotInitialization, IsNotTake);
      }
   }

   void assignWithTake(IRGenFunction &IGF, Address dest, Address src, PILType T,
                       bool isOutlined) const override {
      // If the fields are not ABI-accessible, use the value witness table.
      if (!AreFieldsABIAccessible) {
         return emitAssignWithTakeCall(IGF, T, dest, src);
      }

      if (isOutlined || T.hasOpenedExistential()) {
         auto offsets = asImpl().getNonFixedOffsets(IGF, T);
         for (auto &field : getFields()) {
            if (field.isEmpty())
               continue;

            Address destField = field.projectAddress(IGF, dest, offsets);
            Address srcField = field.projectAddress(IGF, src, offsets);
            field.getTypeInfo().assignWithTake(
               IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined);
         }
      } else {
         this->callOutlinedCopy(IGF, dest, src, T, IsNotInitialization, IsTake);
      }
   }

   void initializeWithCopy(IRGenFunction &IGF, Address dest, Address src,
                           PILType T, bool isOutlined) const override {
      // If we're POD, use the generic routine.
      if (this->isPOD(ResilienceExpansion::Maximal) &&
          isa<LoadableTypeInfo>(this)) {
         return cast<LoadableTypeInfo>(this)->LoadableTypeInfo::initializeWithCopy(
            IGF, dest, src, T, isOutlined);
      }

      // If the fields are not ABI-accessible, use the value witness table.
      if (!AreFieldsABIAccessible) {
         return emitInitializeWithCopyCall(IGF, T, dest, src);
      }

      if (isOutlined || T.hasOpenedExistential()) {
         auto offsets = asImpl().getNonFixedOffsets(IGF, T);
         for (auto &field : getFields()) {
            if (field.isEmpty())
               continue;

            Address destField = field.projectAddress(IGF, dest, offsets);
            Address srcField = field.projectAddress(IGF, src, offsets);
            field.getTypeInfo().initializeWithCopy(
               IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined);
         }
      } else {
         this->callOutlinedCopy(IGF, dest, src, T, IsInitialization, IsNotTake);
      }
   }

   void initializeWithTake(IRGenFunction &IGF, Address dest, Address src,
                           PILType T, bool isOutlined) const override {
      // If we're bitwise-takable, use memcpy.
      if (this->isBitwiseTakable(ResilienceExpansion::Maximal)) {
         IGF.Builder.CreateMemCpy(dest.getAddress(),
                                  dest.getAlignment().getValue(),
                                  src.getAddress(),
                                  src.getAlignment().getValue(),
                                  asImpl().Impl::getSize(IGF, T));
         return;
      }

      // If the fields are not ABI-accessible, use the value witness table.
      if (!AreFieldsABIAccessible) {
         return emitInitializeWithTakeCall(IGF, T, dest, src);
      }

      if (isOutlined || T.hasOpenedExistential()) {
         auto offsets = asImpl().getNonFixedOffsets(IGF, T);
         for (auto &field : getFields()) {
            if (field.isEmpty())
               continue;

            Address destField = field.projectAddress(IGF, dest, offsets);
            Address srcField = field.projectAddress(IGF, src, offsets);
            field.getTypeInfo().initializeWithTake(
               IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined);
         }
      } else {
         this->callOutlinedCopy(IGF, dest, src, T, IsInitialization, IsTake);
      }
   }

   void destroy(IRGenFunction &IGF, Address addr, PILType T,
                bool isOutlined) const override {
      // If the fields are not ABI-accessible, use the value witness table.
      if (!AreFieldsABIAccessible) {
         return emitDestroyCall(IGF, T, addr);
      }

      if (isOutlined || T.hasOpenedExistential()) {
         auto offsets = asImpl().getNonFixedOffsets(IGF, T);
         for (auto &field : getFields()) {
            if (field.isPOD())
               continue;

            field.getTypeInfo().destroy(IGF,
                                        field.projectAddress(IGF, addr, offsets),
                                        field.getType(IGF.IGM, T), isOutlined);
         }
      } else {
         this->callOutlinedDestroy(IGF, addr, T);
      }
   }

   // The extra inhabitants of a record are determined from its fields.
   bool mayHaveExtraInhabitants(IRGenModule &IGM) const override {
      if (!MayHaveExtraInhabitants.hasValue()) {
         MayHaveExtraInhabitants = false;
         for (auto &field : asImpl().getFields())
            if (field.getTypeInfo().mayHaveExtraInhabitants(IGM)) {
               MayHaveExtraInhabitants = true;
               break;
            }
      }
      return *MayHaveExtraInhabitants;
   }

   // Perform an operation using the field that provides extra inhabitants for
   // the aggregate, whether that field is known statically or dynamically.
   llvm::Value *withExtraInhabitantProvidingField(IRGenFunction &IGF,
                                                  Address structAddr,
                                                  PILType structType,
                                                  llvm::Value *knownStructNumXI,
                                                  llvm::Type *resultTy,
                                                  llvm::function_ref<llvm::Value* (const FieldImpl &field,
                                                                                   llvm::Value *numXI)> body) const {
      // If we know one field consistently provides extra inhabitants, delegate
      // to that field.
      if (auto field = asImpl().getFixedExtraInhabitantProvidingField(IGF.IGM)){
         return body(*field, knownStructNumXI);
      }

      // Otherwise, we have to figure out which field at runtime.

      // The number of extra inhabitants the instantiated type has can be used
      // to figure out which field the runtime chose. The runtime uses the same
      // algorithm as above--use the field with the most extra inhabitants,
      // favoring the earliest field in a tie. If we test the number of extra
      // inhabitants in the struct against each field type's, then the first
      // match should indicate which field we chose.
      //
      // We can reduce the decision space somewhat if there are fixed-layout
      // fields, since we know the only possible runtime choices are
      // either the fixed field with the most extra inhabitants (if any), or
      // one of the unknown-layout fields.
      //
      // See whether we have a fixed candidate.
      const FieldImpl *fixedCandidate = nullptr;
      unsigned fixedCount = 0;
      for (auto &field : asImpl().getFields()) {
         if (!field.getTypeInfo().mayHaveExtraInhabitants(IGF.IGM))
            continue;

         if (const FixedTypeInfo *fixed =
            dyn_cast<FixedTypeInfo>(&field.getTypeInfo())) {
            auto fieldCount = fixed->getFixedExtraInhabitantCount(IGF.IGM);
            if (fieldCount > fixedCount) {
               fixedCandidate = &field;
               fixedCount = fieldCount;
            }
         }
      }

      // Loop through checking to see whether we picked the fixed candidate
      // (if any) or one of the unknown-layout fields.
      llvm::Value *instantiatedCount
         = (knownStructNumXI
            ? knownStructNumXI
            : emitLoadOfExtraInhabitantCount(IGF, structType));

      auto contBB = IGF.createBasicBlock("chose_field_for_xi");
      llvm::PHINode *contPhi = nullptr;
      if (resultTy != IGF.IGM.VoidTy)
         contPhi = llvm::PHINode::Create(resultTy,
                                         asImpl().getFields().size());

      // If two fields have the same type, they have the same extra inhabitant
      // count, and we'll pick the first. We don't have to check both.
      SmallPtrSet<PILType, 4> visitedTypes;

      for (auto &field : asImpl().getFields()) {
         if (!field.getTypeInfo().mayHaveExtraInhabitants(IGF.IGM))
            continue;

         ConditionalDominanceScope condition(IGF);

         llvm::Value *fieldCount;
         if (isa<FixedTypeInfo>(field.getTypeInfo())) {
            // Skip fixed fields except for the candidate with the most known
            // extra inhabitants we picked above.
            if (&field != fixedCandidate)
               continue;

            fieldCount = IGF.IGM.getInt32(fixedCount);
         } else {
            auto fieldTy = field.getType(IGF.IGM, structType);
            // If this field has the same type as a field we already tested,
            // we'll never pick this one, since they both have the same count.
            if (!visitedTypes.insert(fieldTy).second)
               continue;

            fieldCount = emitLoadOfExtraInhabitantCount(IGF, fieldTy);
         }
         auto equalsCount = IGF.Builder.CreateICmpEQ(instantiatedCount,
                                                     fieldCount);

         auto yesBB = IGF.createBasicBlock("");
         auto noBB = IGF.createBasicBlock("");

         IGF.Builder.CreateCondBr(equalsCount, yesBB, noBB);

         IGF.Builder.emitBlock(yesBB);
         auto value = body(field, instantiatedCount);
         if (contPhi)
            contPhi->addIncoming(value, IGF.Builder.GetInsertBlock());
         IGF.Builder.CreateBr(contBB);

         IGF.Builder.emitBlock(noBB);
      }

      // We shouldn't have picked a number of extra inhabitants inconsistent
      // with any individual field.
      IGF.Builder.CreateUnreachable();

      IGF.Builder.emitBlock(contBB);
      if (contPhi)
         IGF.Builder.Insert(contPhi);

      return contPhi;
   }

   const FieldImpl *
   getFixedExtraInhabitantProvidingField(IRGenModule &IGM) const {
      if (!ExtraInhabitantProvidingField.hasValue()) {
         unsigned mostExtraInhabitants = 0;
         const FieldImpl *fieldWithMost = nullptr;
         const FieldImpl *singleNonFixedField = nullptr;

         // TODO: If two fields have the same type, they have the same extra
         // inhabitant count, and we'll pick the first. We don't have to check
         // both. However, we don't always have access to the substituted struct
         // type from this context, which would be necessary to make that
         // judgment reliably.

         for (auto &field : asImpl().getFields()) {
            auto &ti = field.getTypeInfo();
            if (!ti.mayHaveExtraInhabitants(IGM))
               continue;

            auto *fixed = dyn_cast<FixedTypeInfo>(&field.getTypeInfo());
            // If any field is non-fixed, we can't definitively pick a best one,
            // unless it happens to be the only non-fixed field and none of the
            // other fields have extra inhabitants.
            if (!fixed) {
               // If we already saw a non-fixed field, then we can't pick one
               // at compile time.
               if (singleNonFixedField) {
                  singleNonFixedField = fieldWithMost = nullptr;
                  break;
               }

               // Otherwise, note this field for later. If we have no fixed
               // candidates, it may be the only choice for extra inhabitants.
               singleNonFixedField = &field;
               continue;
            }

            unsigned count = fixed->getFixedExtraInhabitantCount(IGM);
            if (count > mostExtraInhabitants) {
               mostExtraInhabitants = count;
               fieldWithMost = &field;
            }
         }

         if (fieldWithMost) {
            if (singleNonFixedField) {
               // If we have a non-fixed and fixed candidate, we can't know for
               // sure now.
               ExtraInhabitantProvidingField = nullptr;
            } else {
               // If we had all fixed fields, pick the one with the most extra
               // inhabitants.
               ExtraInhabitantProvidingField = fieldWithMost;
            }
         } else {
            // If there were no fixed candidates, but we had a single non-fixed
            // field with potential extra inhabitants, then it's our only choice.
            ExtraInhabitantProvidingField = singleNonFixedField;
         }
      }
      return *ExtraInhabitantProvidingField;
   }

   void collectMetadataForOutlining(OutliningMetadataCollector &collector,
                                    PILType T) const override {
      for (auto &field : getFields()) {
         if (field.isEmpty())
            continue;
         auto fType = field.getType(collector.IGF.IGM, T);
         field.getTypeInfo().collectMetadataForOutlining(collector, fType);
      }
      collector.collectTypeMetadataForLayout(T);
   }
};

template <class Impl, class Base, class FieldImpl_,
   bool IsFixedSize = std::is_base_of<FixedTypeInfo, Base>::value,
   bool IsLoadable = std::is_base_of<LoadableTypeInfo, Base>::value>
class RecordTypeInfo;

/// An implementation of RecordTypeInfo for non-fixed-size types
/// (but not resilient ones where we don't know the complete set of
/// stored properties).
///
/// Override the buffer operations to just delegate to the unique
/// non-empty field, if there is one.
template <class Impl, class Base, class FieldImpl>
class RecordTypeInfo<Impl, Base, FieldImpl,
   /*IsFixedSize*/ false, /*IsLoadable*/ false>
   : public RecordTypeInfoImpl<Impl, Base, FieldImpl> {
   using super = RecordTypeInfoImpl<Impl, Base, FieldImpl>;

   /// The index+1 of the unique non-empty field, or zero if there is none.
   unsigned UniqueNonEmptyFieldIndexPlusOne;
protected:
   template <class... As>
   RecordTypeInfo(ArrayRef<FieldImpl> fields, As&&...args)
      : super(fields, std::forward<As>(args)...) {

      // Look for a unique non-empty field.
      UniqueNonEmptyFieldIndexPlusOne = findUniqueNonEmptyField(fields);
   }

public:
   using super::getStorageType;

   Address initializeBufferWithCopyOfBuffer(IRGenFunction &IGF,
                                            Address destBuffer,
                                            Address srcBuffer,
                                            PILType type) const override {
      if (auto field = getUniqueNonEmptyField()) {
         auto &fieldTI = field->getTypeInfo();
         Address fieldResult =
            fieldTI.initializeBufferWithCopyOfBuffer(IGF, destBuffer, srcBuffer,
                                                     field->getType(IGF.IGM, type));
         return IGF.Builder.CreateElementBitCast(fieldResult, getStorageType());
      } else {
         return super::initializeBufferWithCopyOfBuffer(IGF, destBuffer,
                                                        srcBuffer, type);
      }
   }

private:
   /// Scan the given field info
   static unsigned findUniqueNonEmptyField(ArrayRef<FieldImpl> fields) {
      unsigned result = 0;
      for (auto &field : fields) {
         // Ignore empty fields.
         if (field.isEmpty()) continue;

         // If the field is not ABI-accessible, suppress this.
         if (!field.isABIAccessible()) continue;

         // If we've already found an index, then there isn't a
         // unique non-empty field.
         if (result) return 0;

         result = (&field - fields.data()) + 1;
      }

      return result;
   }

   const FieldImpl *getUniqueNonEmptyField() const {
      if (UniqueNonEmptyFieldIndexPlusOne) {
         return &this->getFields()[UniqueNonEmptyFieldIndexPlusOne - 1];
      } else {
         return nullptr;
      }
   }
};

/// An implementation of RecordTypeInfo for fixed-layout types that
/// aren't necessarily loadable.
template <class Impl, class Base, class FieldImpl>
class RecordTypeInfo<Impl, Base, FieldImpl,
   /*IsFixedSize*/ true, /*IsLoadable*/ false>
   : public RecordTypeInfoImpl<Impl, Base, FieldImpl> {
   using super = RecordTypeInfoImpl<Impl, Base, FieldImpl>;
protected:
   template <class... As>
   RecordTypeInfo(ArrayRef<FieldImpl> fields, As &&...args)
      : super(fields, FieldsAreABIAccessible, std::forward<As>(args)...) {}

   using super::asImpl;

public:
   unsigned getFixedExtraInhabitantCount(IRGenModule &IGM) const override {
      if (auto field = asImpl().getFixedExtraInhabitantProvidingField(IGM)) {
         auto &fieldTI = cast<FixedTypeInfo>(field->getTypeInfo());
         return fieldTI.getFixedExtraInhabitantCount(IGM);
      }

      return 0;
   }

   APInt getFixedExtraInhabitantValue(IRGenModule &IGM,
                                      unsigned bits,
                                      unsigned index) const override {
      // We are only called if the type is known statically to have extra
      // inhabitants.
      auto &field = *asImpl().getFixedExtraInhabitantProvidingField(IGM);
      auto &fieldTI = cast<FixedTypeInfo>(field.getTypeInfo());
      auto fieldSize = fieldTI.getFixedExtraInhabitantMask(IGM).getBitWidth();

      auto value = BitPatternBuilder(IGM.Triple.isLittleEndian());
      value.appendClearBits(field.getFixedByteOffset().getValueInBits());
      value.append(fieldTI.getFixedExtraInhabitantValue(IGM, fieldSize, index));
      value.padWithClearBitsTo(bits);
      return value.build().getValue();
   }

   APInt getFixedExtraInhabitantMask(IRGenModule &IGM) const override {
      auto field = asImpl().getFixedExtraInhabitantProvidingField(IGM);
      if (!field)
         return APInt();

      const FixedTypeInfo &fieldTI
         = cast<FixedTypeInfo>(field->getTypeInfo());
      auto targetSize = asImpl().getFixedSize().getValueInBits();

      if (fieldTI.isKnownEmpty(ResilienceExpansion::Maximal))
         return APInt(targetSize, 0);

      auto mask = BitPatternBuilder(IGM.Triple.isLittleEndian());
      mask.appendClearBits(field->getFixedByteOffset().getValueInBits());
      mask.append(fieldTI.getFixedExtraInhabitantMask(IGM));
      mask.padWithClearBitsTo(targetSize);
      return mask.build().getValue();
   }

   llvm::Value *getExtraInhabitantIndex(IRGenFunction &IGF,
                                        Address structAddr,
                                        PILType structType,
                                        bool isOutlined) const override {
      auto field = *asImpl().getFixedExtraInhabitantProvidingField(IGF.IGM);
      Address fieldAddr =
         asImpl().projectFieldAddress(IGF, structAddr, structType, field);
      auto &fieldTI = cast<FixedTypeInfo>(field.getTypeInfo());
      return fieldTI.getExtraInhabitantIndex(IGF, fieldAddr,
                                             field.getType(IGF.IGM, structType),
                                             false /*not outlined for field*/);
   }

   void storeExtraInhabitant(IRGenFunction &IGF,
                             llvm::Value *index,
                             Address structAddr,
                             PILType structType,
                             bool isOutlined) const override {
      auto field = *asImpl().getFixedExtraInhabitantProvidingField(IGF.IGM);
      Address fieldAddr =
         asImpl().projectFieldAddress(IGF, structAddr, structType, field);
      auto &fieldTI = cast<FixedTypeInfo>(field.getTypeInfo());
      fieldTI.storeExtraInhabitant(IGF, index, fieldAddr,
                                   field.getType(IGF.IGM, structType),
                                   false /*not outlined for field*/);
   }
};

/// An implementation of RecordTypeInfo for loadable types.
template <class Impl, class Base, class FieldImpl>
class RecordTypeInfo<Impl, Base, FieldImpl,
   /*IsFixedSize*/ true, /*IsLoadable*/ true>
   : public RecordTypeInfo<Impl, Base, FieldImpl, true, false> {
   using super = RecordTypeInfo<Impl, Base, FieldImpl, true, false>;

   unsigned ExplosionSize : 16;

protected:
   using super::asImpl;

   template <class... As>
   RecordTypeInfo(ArrayRef<FieldImpl> fields,
                  unsigned explosionSize,
                  As &&...args)
      : super(fields, std::forward<As>(args)...),
        ExplosionSize(explosionSize) {}

private:
   template <void (LoadableTypeInfo::*Op)(IRGenFunction &IGF,
                                          Address addr,
                                          Explosion &out) const>
   void forAllFields(IRGenFunction &IGF, Address addr, Explosion &out) const {
      auto offsets = asImpl().getNonFixedOffsets(IGF);
      for (auto &field : getFields()) {
         if (field.isEmpty()) continue;

         Address fieldAddr = field.projectAddress(IGF, addr, offsets);
         (cast<LoadableTypeInfo>(field.getTypeInfo()).*Op)(IGF, fieldAddr, out);
      }
   }

   template <void (LoadableTypeInfo::*Op)(IRGenFunction &IGF, Address addr,
                                          Explosion &out, Atomicity atomicity) const>
   void forAllFields(IRGenFunction &IGF, Address addr, Explosion &out,
                     Atomicity atomicity) const {
      auto offsets = asImpl().getNonFixedOffsets(IGF);
      for (auto &field : getFields()) {
         if (field.isEmpty()) continue;

         Address fieldAddr = field.projectAddress(IGF, addr, offsets);
         (cast<LoadableTypeInfo>(field.getTypeInfo()).*Op)(IGF, fieldAddr, out,
                                                           atomicity);
      }
   }

   template <void (LoadableTypeInfo::*Op)(IRGenFunction &IGF, Explosion &in,
                                          Address addr, bool isOutlined) const>
   void forAllFields(IRGenFunction &IGF, Explosion &in, Address addr,
                     bool isOutlined) const {
      auto offsets = asImpl().getNonFixedOffsets(IGF);
      for (auto &field : getFields()) {
         if (field.isEmpty()) continue;

         Address fieldAddr = field.projectAddress(IGF, addr, offsets);
         (cast<LoadableTypeInfo>(field.getTypeInfo()).*Op)(IGF, in, fieldAddr,
                                                           isOutlined);
      }
   }

public:
   using super::getFields;

   void loadAsCopy(IRGenFunction &IGF, Address addr,
                   Explosion &out) const override {
      forAllFields<&LoadableTypeInfo::loadAsCopy>(IGF, addr, out);
   }

   void loadAsTake(IRGenFunction &IGF, Address addr,
                   Explosion &out) const override {
      forAllFields<&LoadableTypeInfo::loadAsTake>(IGF, addr, out);
   }

   void assign(IRGenFunction &IGF, Explosion &e, Address addr,
               bool isOutlined) const override {
      forAllFields<&LoadableTypeInfo::assign>(IGF, e, addr, isOutlined);
   }

   void initialize(IRGenFunction &IGF, Explosion &e, Address addr,
                   bool isOutlined) const override {
      forAllFields<&LoadableTypeInfo::initialize>(IGF, e, addr, isOutlined);
   }

   unsigned getExplosionSize() const override {
      return ExplosionSize;
   }

   void reexplode(IRGenFunction &IGF, Explosion &src,
                  Explosion &dest) const override {
      for (auto &field : getFields())
         cast<LoadableTypeInfo>(field.getTypeInfo()).reexplode(IGF, src, dest);
   }

   void copy(IRGenFunction &IGF, Explosion &src,
             Explosion &dest, Atomicity atomicity) const override {
      for (auto &field : getFields())
         cast<LoadableTypeInfo>(field.getTypeInfo())
            .copy(IGF, src, dest, atomicity);
   }

   void consume(IRGenFunction &IGF, Explosion &src,
                Atomicity atomicity) const override {
      for (auto &field : getFields())
         cast<LoadableTypeInfo>(field.getTypeInfo())
            .consume(IGF, src, atomicity);
   }

   void fixLifetime(IRGenFunction &IGF, Explosion &src) const override {
      for (auto &field : getFields())
         cast<LoadableTypeInfo>(field.getTypeInfo()).fixLifetime(IGF, src);
   }

   void packIntoEnumPayload(IRGenFunction &IGF,
                            EnumPayload &payload,
                            Explosion &src,
                            unsigned startOffset) const override {
      for (auto &field : getFields()) {
         if (!field.isEmpty()) {
            unsigned offset = field.getFixedByteOffset().getValueInBits()
                              + startOffset;
            cast<LoadableTypeInfo>(field.getTypeInfo())
               .packIntoEnumPayload(IGF, payload, src, offset);
         }
      }
   }

   void unpackFromEnumPayload(IRGenFunction &IGF, const EnumPayload &payload,
                              Explosion &dest, unsigned startOffset)
   const override {
      for (auto &field : getFields()) {
         if (!field.isEmpty()) {
            unsigned offset = field.getFixedByteOffset().getValueInBits()
                              + startOffset;
            cast<LoadableTypeInfo>(field.getTypeInfo())
               .unpackFromEnumPayload(IGF, payload, dest, offset);
         }
      }
   }
};

/// A builder of record types.
///
/// Required for a full implementation:
///   TypeInfoImpl *construct(void *buffer, ArrayRef<ASTField> fields);
///   FieldImpl getFieldInfo(const ASTField &field, const TypeInfo &fieldTI);
///   Type getType(const ASTField &field);
///   void performLayout(ArrayRef<const TypeInfo *> fieldTypes);
///     - should call recordLayout with the layout
template <class BuilderImpl, class FieldImpl, class ASTField>
class RecordTypeBuilder {
protected:
   IRGenModule &IGM;
   RecordTypeBuilder(IRGenModule &IGM) : IGM(IGM) {}

   BuilderImpl *asImpl() { return static_cast<BuilderImpl*>(this); }

public:
   TypeInfo *layout(ArrayRef<ASTField> astFields) {
      SmallVector<FieldImpl, 8> fields;
      SmallVector<const TypeInfo *, 8> fieldTypesForLayout;
      fields.reserve(astFields.size());
      fieldTypesForLayout.reserve(astFields.size());

      bool loadable = true;
      auto fieldsABIAccessible = FieldsAreABIAccessible;

      unsigned explosionSize = 0;
      for (unsigned i : indices(astFields)) {
         auto &astField = astFields[i];
         // Compute the field's type info.
         auto &fieldTI = IGM.getTypeInfo(asImpl()->getType(astField));
         fieldTypesForLayout.push_back(&fieldTI);

         if (!fieldTI.isABIAccessible())
            fieldsABIAccessible = FieldsAreNotABIAccessible;

         fields.push_back(FieldImpl(asImpl()->getFieldInfo(i, astField, fieldTI)));

         auto loadableFieldTI = dyn_cast<LoadableTypeInfo>(&fieldTI);
         if (!loadableFieldTI) {
            loadable = false;
            continue;
         }

         auto &fieldInfo = fields.back();
         fieldInfo.Begin = explosionSize;
         explosionSize += loadableFieldTI->getExplosionSize();
         fieldInfo.End = explosionSize;
      }

      // Perform layout and fill in the fields.
      StructLayout layout = asImpl()->performLayout(fieldTypesForLayout);
      for (unsigned i = 0, e = fields.size(); i != e; ++i) {
         fields[i].completeFrom(layout.getElements()[i]);
      }

      // Create the type info.
      if (loadable) {
         assert(layout.isFixedLayout());
         assert(fieldsABIAccessible);
         return asImpl()->createLoadable(fields, std::move(layout), explosionSize);
      } else if (layout.isFixedLayout()) {
         assert(fieldsABIAccessible);
         return asImpl()->createFixed(fields, std::move(layout));
      } else {
         return asImpl()->createNonFixed(fields, fieldsABIAccessible,
                                         std::move(layout));
      }
   }
};

} // end namespace polar::irgen

#endif // POLARPHP_IRGEN_INTERNAL_GENRECORD_H
